////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2012 Kentaro Sekimoto  All rights reserved.
////////////////////////////////////////////////////////////////////////////

#include <tinyhal.h>
#include "..\FM4.h"

//#define DEBUG_PWM
/*
 *  Register Bit Definition
 */
#define TMCR_STRG      0x0001U
#define TMCR_CTEN      0x0002U
#define TMCR_MDSE      0x0004U
#define TMCR_OSEL      0x0008U
#define TMCR_FMD_RST   0x0000U
#define TMCR_FMD_PWM   0x0010U
#define TMCR_EGS_DIS   0x0000U
#define TMCR_EGS_RISE  0x0100U
#define TMCR_EGS_FALL  0x0200U
#define TMCR_EGS_BOTH  0x0300U
#define TMCR_PMSK      0x0400U
#define TMCR_RTGEN     0x0800U
#define TMCR_CKS02_0   0x0000U
#define TMCR_CKS02_1   0x1000U
#define TMCR_CKS02_2   0x2000U
#define TMCR_CKS02_3   0x3000U
#define TMCR_CKS02_4   0x4000U
#define TMCR_CKS02_5   0x5000U
#define TMCR_CKS02_6   0x6000U
#define TMCR_CKS02_7   0x7000U

#define TMCR2_CKS3_0   0x00U
#define TMCR2_CKS3_1   0x01U

#define STC_UDIR       0x01U
#define STC_DTIR       0x02U
#define STC_TGIR       0x04U
#define STC_UDIE       0x10U
#define STC_DTIE       0x20U
#define STC_TGIE       0x40U
#define STC_IRQF       (STC_UDIR | STC_DTIR | STC_TGIR)

// TMCR
// b0: STRG
// b1: CTEN
// b2: MDSE
// b3: OSEL
// b4: FMD0
// b5: FMD1
// b6: FMD2
// b7: reserved
// b8: EGS0
// b9: EGS1
// b10:PMSK
// b11:RTGEN
// b12:CKS0
// b13:CKS1
// b14:CKS2
// b15 reserved

static const UINT8 PWM_ID2APIN[] = {
    0x40, 0x41, 0x42, 0x43, 0x44, 0x25, 0x72, 0x64,
    0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x0c, 0x23,
    0x07, 0x53, 0x60, 0x09, 0x70, 0x0d, 0x27, 0x67
};

#if 0
static const UINT8 PWM_ID2BPIN[] = {
    0x45, 0x4b, 0x4c, 0x4d, 0x4e, 0x26, 0x73, 0x65,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x0b, 0x22,
    0x06, 0x54, 0x61, 0x08, 0x71, 0x0e, 0x1f, 0x68
};
#endif

static const UINT8 PWM_CH2ID[] = {
    8*0+0,  // TIOA0_0 P40
    8*0+1,  // TIOA1_0 P41
    8*0+4,  // TIOA4_0 P44
    8*0+5,  // TIOA5_0 P25
    8*0+6,  // TIOA6_0 P72
    8*0+7,  // TIOA7_0 P64
    8*2+6,  // TIOA6_2 P27
    8*2+7   // TIOA7_2 P67
};

static const UINT8 PWM_BTCH2ASHIFT[] = {
    2, 10, 18, 26, 34, 42, 50, 58
};

struct FM4_PWM_Driver
{
    static const UINT32 c_Channels = 8;
//    void FM4_PWM_Driver();
    BOOL    Initialize        ( PWM_CHANNEL channel );
    BOOL    Uninitialize      ( PWM_CHANNEL channel );
#ifdef NETMF42_RTM
    BOOL    ApplyConfiguration( PWM_CHANNEL channel, GPIO_PIN pin, UINT32& period, UINT32& duration, BOOL invert );
#else
    BOOL    ApplyConfiguration( PWM_CHANNEL channel, GPIO_PIN pin, UINT32& period, UINT32& duration, PWM_SCALE_FACTOR& scale, BOOL invert );
#endif
    BOOL    Start             ( PWM_CHANNEL channel, GPIO_PIN pin );
    void    Stop              ( PWM_CHANNEL channel, GPIO_PIN pin );
    BOOL    Start             ( PWM_CHANNEL* channel, GPIO_PIN* pin, UINT32 count );
    void    Stop              ( PWM_CHANNEL* channel, GPIO_PIN* pin, UINT32 count );
    UINT32  Channels          ( );
    GPIO_PIN GetPinForChannel  ( PWM_CHANNEL channel );

    void EnablePin(PWM_CHANNEL channel);
    void DisablePin(PWM_CHANNEL channel);

    static FM4_BT_PWM_TypeDef *c_PWM[c_Channels];
};

FM4_BT_PWM_TypeDef *FM4_PWM_Driver::c_PWM[c_Channels] = {
    FM4_BT0_PWM,
    FM4_BT1_PWM,
    FM4_BT2_PWM,
    FM4_BT3_PWM,
    FM4_BT4_PWM,
    FM4_BT5_PWM,
    FM4_BT6_PWM,
    FM4_BT7_PWM,
};

FM4_PWM_Driver g_FM4_PWM_Driver;

BOOL PWM_Initialize(PWM_CHANNEL channel)
{
    return g_FM4_PWM_Driver.Initialize(channel);
}

BOOL PWM_Uninitialize(PWM_CHANNEL channel)
{
    return g_FM4_PWM_Driver.Uninitialize(channel);
}

#ifdef NETMF42_RTM
BOOL PWM_ApplyConfiguration(PWM_CHANNEL channel, GPIO_PIN pin, UINT32& period, UINT32& duration, BOOL invert)
#else
BOOL PWM_ApplyConfiguration(PWM_CHANNEL channel, GPIO_PIN pin, UINT32& period, UINT32& duration, PWM_SCALE_FACTOR& scale, BOOL invert)
#endif
{
    return g_FM4_PWM_Driver.ApplyConfiguration(channel, pin, period, duration, scale, invert);
}

BOOL PWM_Start(PWM_CHANNEL channel, GPIO_PIN pin)
{
    return g_FM4_PWM_Driver.Start(channel,pin);
}

void PWM_Stop(PWM_CHANNEL channel, GPIO_PIN pin)
{
    return g_FM4_PWM_Driver.Stop(channel,pin);
}

BOOL PWM_Start(PWM_CHANNEL* channel, GPIO_PIN* pin, UINT32 count)
{
    return g_FM4_PWM_Driver.Start(channel, pin, count);
}

void PWM_Stop(PWM_CHANNEL* channel, GPIO_PIN* pin, UINT32 count)
{
    return g_FM4_PWM_Driver.Stop(channel, pin, count);
}

UINT32 PWM_PWMChannels() 
{
    return g_FM4_PWM_Driver.Channels();
}

GPIO_PIN PWM_GetPinForChannel(PWM_CHANNEL channel)
{
    return g_FM4_PWM_Driver.GetPinForChannel(channel);
}

//--//

BOOL FM4_PWM_Driver::Initialize(PWM_CHANNEL channel)
{
    return TRUE;
}

BOOL FM4_PWM_Driver::Uninitialize(PWM_CHANNEL channel)
{
    DisablePin(channel);
    return TRUE;
}

UINT32 PWM_Clkdev_Get(PWM_SCALE_FACTOR& scale, UINT32 period)
{
    UINT32 clkdev;
#if defined(MB9BF568R)
    if (scale == PWM_NANOSECONDS) {
        if (period < 100000)
            clkdev = 1;
        else if (period < 1000000)
            clkdev = 4;
        else if (period < 10000000)
            clkdev = 16;
        else if (period < 100000000)
            clkdev = 128;
        else
            clkdev = 2048;
    } else if (scale == PWM_MICROSECONDS) {
        if (period < 100)
            clkdev = 1;
        else if (period < 1000)
            clkdev = 4;
        else if (period < 10000)
            clkdev = 16;
        else if (period < 100000)
            clkdev = 128;
        else
            clkdev = 2048;
    } else {
        if (period < 10)
            clkdev = 16;
        else if (period < 100)
            clkdev = 128;
        else
            clkdev = 2048;
    }
#endif
    return clkdev;
}

void PWM_Clock_Set(FM4_BT_PWM_TypeDef *pwm, UINT32 clkdev, BOOL invert)
{
    UINT16 cks;
    switch (clkdev) {
    case 4:
        cks = 1;
        break;
    case 16:
        cks = 2;
        break;
    case 128:
        cks = 3;
        break;
    case 256:
        cks = 4;
        break;
    case 512:
        cks = 8;
        break;
    case 1024:
        cks = 9;
        break;
    case 2048:
        cks = 10;
        break;
    case 1:
    default:
        cks = 0;
        break;
    }
    pwm->TMCR = 0x0810 + ((cks & 0x7) << 12);
    if (invert)
        pwm->TMCR |= 0x4;
    pwm->TMCR2 = (cks >> 3);
}

#ifdef NETMF42_RTM
BOOL FM4_PWM_Driver::ApplyConfiguration(PWM_CHANNEL channel, GPIO_PIN pin, UINT32& period, UINT32& duration, BOOL invert)
#else
BOOL FM4_PWM_Driver::ApplyConfiguration(PWM_CHANNEL channel, GPIO_PIN pin, UINT32& period, UINT32& duration, PWM_SCALE_FACTOR& scale, BOOL invert)
#endif
{
    UINT32 clkdev;
    //if (scale == PWM_NANOSECONDS) return FALSE;
    if ((UINT32)channel >= c_Channels)
        return FALSE;
    UINT32 flag_started = FALSE;
    UINT32 period_ticks;
    UINT32 duration_ticks;
    UINT32 pwm_id;
    UINT32 pwm_btch;
    UINT32 pwm_mode;
    UINT32 pwm_pin;
    UINT32 epfr_shift;
    UINT32 epff_mask;

    pwm_id = PWM_CH2ID[(UINT32)channel];
    pwm_btch = pwm_id % 8;
    pwm_mode = (pwm_id) / 8 + 1;
    pwm_pin = PWM_ID2APIN[pwm_id];
    FM4_BT_PWM_TypeDef *pwm = c_PWM[pwm_btch];
    epfr_shift = PWM_BTCH2ASHIFT[pwm_btch];
    if (epfr_shift < 32) {
        FM4_GPIO->EPFR04 &= ~(3 << epfr_shift);
        FM4_GPIO->EPFR04 |= (pwm_mode << epfr_shift);
    } else {
        FM4_GPIO->EPFR05 &= ~(3 << (epfr_shift - 32));
        FM4_GPIO->EPFR05 |= (pwm_mode << (epfr_shift - 32));
    }
    if ((pwm->TMCR & TMCR_CTEN) != 0) {
        flag_started = TRUE;
        pwm->TMCR &= ~(TMCR_CTEN);
    }
    clkdev = PWM_Clkdev_Get(scale, period);
    PWM_Clock_Set(pwm, clkdev , invert);
    UINT32 ratio;
    if ((SYSTEM_CLOCK_HZ/2) < scale) {
        ratio = scale /(SYSTEM_CLOCK_HZ/2);
        period_ticks = (period / ratio) / clkdev;
        duration_ticks = (duration / ratio) / clkdev;
    } else {
        ratio = (SYSTEM_CLOCK_HZ/2) / scale;
        period_ticks = (period * ratio) / clkdev;
        duration_ticks = (duration * ratio) / clkdev;
    }
#ifdef DEBUG_PWM
    lcd_printf("P=%08x C=%d S=%d    \r\n", period, clkdev, scale);
    lcd_printf("p=%08x d=%08x\r\n", period_ticks, duration_ticks);
#endif
    if (period_ticks == 0 || period_ticks > 0xffff) {
        lcd_printf("Unsuppored Freq!\r\n");
        return FALSE;
    }
    pwm->STC = 0;
    pwm->PCSR = (UINT16)period_ticks;
    pwm->PDUT = (UINT16)duration_ticks;
    if (flag_started == TRUE) {
        pwm->TMCR |= (TMCR_CTEN | TMCR_STRG);
    }
    return TRUE;
}

void FM4_PWM_Driver::EnablePin(PWM_CHANNEL channel)
{
    UINT32 pwm_pin;
    pwm_pin = PWM_ID2APIN[PWM_CH2ID[(UINT32)channel]];
    GPIO_PFR(GPIO_PORT(pwm_pin)) |= GPIO_MASK(pwm_pin);
    GPIO_PZR(GPIO_PORT(pwm_pin)) |= GPIO_MASK(pwm_pin);
}

void FM4_PWM_Driver::DisablePin(PWM_CHANNEL channel)
{
    UINT32 pwm_pin;
    pwm_pin = PWM_ID2APIN[PWM_CH2ID[(UINT32)channel]];
    GPIO_PFR(GPIO_PORT(pwm_pin)) &= ~GPIO_MASK(pwm_pin);
    GPIO_DDR(GPIO_PORT(pwm_pin)) &= ~GPIO_MASK(pwm_pin);
    GPIO_PZR(GPIO_PORT(pwm_pin)) &= ~GPIO_MASK(pwm_pin);
}

BOOL FM4_PWM_Driver::Start(PWM_CHANNEL channel, GPIO_PIN pin)
{
    UINT32 pwm_id;
    UINT32 pwm_btch;
    EnablePin(channel);
    pwm_id = PWM_CH2ID[(UINT32)channel];
    pwm_btch = pwm_id % 8;
    FM4_BT_PWM_TypeDef *pwm = c_PWM[pwm_btch];
    pwm->TMCR |= (TMCR_CTEN | TMCR_STRG);
    return TRUE;
}

void FM4_PWM_Driver::Stop(PWM_CHANNEL channel, GPIO_PIN pin)
{
    UINT32 pwm_id;
    UINT32 pwm_btch;
    pwm_id = PWM_CH2ID[(UINT32)channel];
    pwm_btch = pwm_id % 8;
    FM4_BT_PWM_TypeDef *pwm = c_PWM[pwm_btch];
    pwm->TMCR &= ~(TMCR_CTEN | TMCR_STRG);
    DisablePin(channel);
}

BOOL FM4_PWM_Driver::Start(PWM_CHANNEL* channel, GPIO_PIN* pin, UINT32 count)
{
    for (UINT32 i = 0; i < count; i++) {
        Start(*channel++, *pin++);
    }
    return TRUE;
}

void FM4_PWM_Driver::Stop(PWM_CHANNEL* channel, GPIO_PIN* pin, UINT32 count)
{
    for (UINT32 i = 0; i < count; i++) {
        Stop(*channel++, *pin++);
    }
}

UINT32 FM4_PWM_Driver::Channels()
{
    return c_Channels;
}

GPIO_PIN FM4_PWM_Driver::GetPinForChannel(PWM_CHANNEL channel)
{
    if ((UINT32)channel >= c_Channels)
        return GPIO_PIN_NONE;
    return (GPIO_PIN)PWM_ID2APIN[PWM_CH2ID[channel]];
}
